/*
 * 
 */
(function() {

	var p = {
		'0a': [0],
		'1a': [16],
		'1b': [1],
		'2a': [48, 80],
		'2b': [17, 20],
		'2c': [3, 5],
		'3a': [112],
		'3b': [49, 50, 56, 81],
		'3c': [19, 21, 22, 28],
		'3d': [7],
		'4a': [240],
		'4b': [113, 114],
		'4c': [51, 53, 57, 58, 83, 85, 86],
		'4d': [23, 29],
		'4e': [15],
		'5b': [241],
		'5c': [115, 117, 118, 121],
		'5d': [55, 59, 61, 87],
		'5e': [31],
		'6c': [243, 245],
		'6d': [119, 123],
		'6e': [63, 95],
		'7d': [247],
		'7e': [127],
		'8e': [255],
		'0': [0],
		'1': [1, 16],
		'2': [3, 5, 17, 20, 48, 80],
		'3': [7, 19, 21, 22, 28, 49, 50, 56, 81, 112],
		'4': [15, 23, 29, 51, 53, 57, 58, 83, 85, 86, 113, 114, 240],
		'5': [31, 55, 59, 61, 87, 115, 117, 118, 121, 241],
		'6': [63, 95, 119, 123, 243, 245],
		'7': [127, 247],
		'8': [255],
		'0A': [0], 
		'1A': [1], '1B': [16], 
		'2A': [3], '2B': [5], '2C': [17], '2D': [20], '2E': [48], '2F': [80], 
		'3A': [7], '3B': [19], '3C': [21], '3D': [22], '3E': [28],
		'3F': [49],'3G': [50], '3H': [56], '3I': [81], '3J': [112], 
		'4A': [15], '4B': [23], '4C': [29], '4D': [51], '4E': [53],
		'4F': [57], '4G': [58], '4H': [83], '4I': [85], '4J': [86],
		'4K': [113], '4L': [114], '4M': [240], 
		'5A': [31], '5B': [55], '5C': [59], '5D': [61], '5E': [87],
		'5F': [115], '5G': [117], '5H': [118], '5I': [121], '5J': [241], 
		'6A': [63], '6B': [95], '6C': [119], '6D': [123], '6E': [243], '6F': [245], 
		'7A': [127], '7B': [247], 
		'8A': [255], 
	};

	function toArray(value) {
		if (value instanceof Array) {
			return value;
		}
		return [value];
	}

	function rotate(n) {
		return ((n & 0x70) << 1) |
			((n & 0x80) >> 3) |
			((n & 0x7) << 1) |
			((n & 0x8) >> 3);
	}

	function flip(n) {
		return ((n & 0xa) |
			((n & 0xa0) >> 1) |
			((n & 0x50) << 1) |
			(n & 0x4) >> 2) |
			((n & 0x1) << 2);
	}

	/*
	 * Rule
	 */
	function Rule(name, values, next) {
		this.name = name;
		this.values = values;
		this.next = next;
	}

	Rule.create = function(name, condition) {
		var t = {};
		var values = [];
		for (var status in condition) {
			var value = condition[status];
			if (isNaN(value)) {
				t[status] = createValiableStatus(value);
			} else {
				t[status] = createFixedStatus(value);
			}
			values.push(status);
		}
		var next = function(x, y, universe) {
			var s = t[universe.get(x, y)];
			if (s) {
				return s(x, y, universe);
			} else {
				return 0;
			}
		};
		return new Rule(name, values, next);
	};
	
	var Filter = {
		select: function(statuses) {
			var s = {};
			for (var i in statuses) {
				s[statuses[i]] = true;
			} 
			return function(status) {
				return null != s[status];
			};
		},
		fixed: function(fixedStatus) {
			return function(status) {
				return status != fixedStatus;
			};
		},
		moreThanZero: function(status) {
			return 0 < status;
		},
		onlyOne: function(status) {
			return 1 == status;
		},
	};

	function createValiableStatus(condition) {
		var sf = new ValiableStatusFactory();
		var filter = Filter.onlyOne;
		var defaultValue = 0;
		for (var key in condition) {
			var value = condition[key];
			if ('f' == key) {
				if (value instanceof Array) {
					filter = Filter.select(value);
				} else {
					filter = Filter.fixed(value);
				}
			} else {
				if ('d' == value) {
					defaultValue = key;
				} else {
					var values = value.split(".");
					for (var i in values) {
						var v = values[i];
						var first = v.charAt(0);
						if ("t" == first) {
							for (var j = 1; j < v.length; j++) {
								sf.setTotal8(key, v.charAt(j));
							}
						} else if ("x" == first) {
							for (var j = 1; j < v.length; j+=2) {
								sf.setFlipRotate(key, parseInt(v.substr(j, 2), 16));
							}
						} else if ("n" == first) {
							for (var j = 1; j < v.length; j+=2) {
								sf.set(key, parseInt(v.substr(j, 2), 16));
							}
						} 
					}
				}
			}
		}
		var statuses = sf.statuses;
		return function(x, y, universe) {
			var neighborhood = universe.getNeighborhood(x, y, filter);
			return statuses[neighborhood.number] || defaultValue;
		};
	}

	function ValiableStatusFactory() {
		this.statuses = [];
	}

	ValiableStatusFactory.prototype = {
		set: function(nextStatus, neighborhood) {
			neighborhood = toArray(neighborhood);
			for (var i in neighborhood) {
				var n = neighborhood[i];
				this.statuses[n] = nextStatus;
			}
		},
		// totals = [1,2,3]
		setTotal8: function(nextStatus, total) {
			total = toArray(total);
			for (var i in total) {
				var t = total[i];
				var eights = akinao.ca.Neighborhood.getTotals8(t); 
				for (j in eights) {
					var n = eights[j];
					this.statuses[n] = nextStatus;
				}
			}
		},
		setFlipRotate: function(nextStatus, neighborhood) {
			neighborhood = toArray(neighborhood);
			for (var i in neighborhood) {
				var n = neighborhood[i];
				var r = n;
				var f = flip(n);
				for (var j = 0; j < 4; j++) {
					this.statuses[r] = nextStatus;
					this.statuses[f] = nextStatus;
					r = rotate(r);
					f = rotate(f);
				}
			}
		},
	};

	/*
	 * FixedStatus
	 */
	function createFixedStatus(status) {
		return function(x, y, universe) {
			return status;
		};
	}

	Rule.random = function(n) {
		var next;
		if (akinao.lang.isNumber(n)) {
			next = function(x, y, universe) {
				return n > Math.random() ? 1 : 0;
			};
		} else if (akinao.lang.isArray(n)) {
			var a = [];
			var c = 0;
			for (var i in n) {
				for (var j = 0; j < n[i]; j++) {
					a.push(c);
				}
				c++;
			}
			var len = a.length;
			next = function(x, y, universe) {
				return a[parseInt(Math.random() * len)];
			};
		} else if (akinao.lang.isObject(n)){
			var a = [];
			for (var i in n) {
				for (var j = 0; j < n[i]; j++) {
					a.push(i);
				}
			}
			var len = a.length;
			next = function(x, y, universe) {
				return a[parseInt(Math.random() * len)];
			};
		}
		return new Rule("random", 1, next);
	};

	akinao.lang.namespace("akinao.ca", {
		Rule: Rule,
		Filter: Filter,
	});

})();